/*
 * Kprobes utility module
 *
 * Copyright (C) 2009 Yoshifumi Shimono. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/kprobes.h>
#include <linux/configfs.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/limits.h>
#include <asm/atomic.h>

#define KPU_INFO(fmt, ...) \
	printk(KERN_INFO "kprobe_util: " fmt, ## __VA_ARGS__)

static int kpu_pre_handler(struct kprobe *kp, struct pt_regs *regs);

struct kpu_item {
	struct config_item ui_item;
	atomic_t ui_sleep;
	struct kprobe ui_kp;
};

static struct kpu_item*
kpu_item_of(struct config_item *item)
{
	return item ? container_of(item, struct kpu_item, ui_item) : NULL;
}

static struct kpu_item*
kpu_item_from_kp(struct kprobe *kp)
{
	return kp ? container_of(kp, struct kpu_item, ui_kp) : NULL;
}

static struct configfs_attribute kpu_item_attr_storeme = {
	.ca_owner = THIS_MODULE,
	.ca_name = "sleep",
	.ca_mode = S_IRUGO | S_IWUSR,
};

static struct configfs_attribute *kpu_item_attrs[] = {
	&kpu_item_attr_storeme,
	NULL,
};

static void
kpu_sleep(int sec)
{
	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout(sec * HZ);
}

static int
kpu_pre_handler(struct kprobe *kp, struct pt_regs *regs)
{
	int sec;
	struct kpu_item *uitem = kpu_item_from_kp(kp);

	if ((sec = atomic_read(&uitem->ui_sleep))) {
		KPU_INFO("%d sec sleep start\n", sec);
		kpu_sleep(sec);
		KPU_INFO("%d sec sleep end\n", sec);
	}
	return 0;
}

static ssize_t
kpu_item_attr_show(struct config_item *item, struct configfs_attribute *attr, char *page)
{
	struct kpu_item *uitem = kpu_item_of(item);

	return sprintf(page, "%d\n", atomic_read(&uitem->ui_sleep));
}

#define KPU_SLEEP_MAX 30 /* [sec] */

static ssize_t
kpu_item_attr_store(struct config_item *item, struct configfs_attribute *attr, const char *page, size_t count)
{
	struct kpu_item *uitem = kpu_item_of(item);
	unsigned long tmp;
	char *p = (char*)page;

	tmp = simple_strtoul(p, &p, 10);
	if (!p || (*p && (*p != '\n'))) {
		return -EINVAL;
	}
	if (tmp > KPU_SLEEP_MAX) {
		return -ERANGE;
	}
	atomic_set(&uitem->ui_sleep, tmp);
	return count;
}

static void
kpu_item_release(struct config_item *item)
{
	struct kpu_item *uitem = kpu_item_of(item);

	unregister_kprobe(&uitem->ui_kp);
	KPU_INFO("kprobe unregistered: %s\n", uitem->ui_kp.symbol_name);
	kfree(uitem->ui_kp.symbol_name);
	kfree(uitem);
}

static struct configfs_item_operations kpu_item_ops = {
	.release		= kpu_item_release,
	.show_attribute		= kpu_item_attr_show,
	.store_attribute	= kpu_item_attr_store,
};

static struct config_item_type kpu_item_type = {
	.ct_item_ops	= &kpu_item_ops,
	.ct_attrs	= kpu_item_attrs,
	.ct_owner	= THIS_MODULE,
};

struct kpu_group {
	struct config_group ug_group;
};

static inline struct kpu_group*
kpu_group_of(struct config_item *item)
{
	return item ? container_of(to_config_group(item), struct kpu_group, ug_group) : NULL;
}

static struct config_item*
kpu_kprobe_make_item(struct config_group *group, const char *name)
{
	int err;
	int len;
	char *buf;
	struct kpu_item *uitem;

	if (!(uitem = kzalloc(sizeof(struct kpu_item), GFP_KERNEL))) {
		return ERR_PTR(-ENOMEM);
	}

	len = strnlen(name, NAME_MAX);
	if (!(buf = (char*)kmalloc(len+1, GFP_KERNEL))) {
		err = -ENOMEM;
		goto free_exit;
	}
	memcpy(buf, name, len);
	buf[len] = '\0';
	uitem->ui_kp.symbol_name = buf;

	atomic_set(&uitem->ui_sleep, 0);
	uitem->ui_kp.pre_handler = kpu_pre_handler;
        if ((err = register_kprobe(&uitem->ui_kp)) < 0) {
                KPU_INFO("register_kprobe failed, returned %d\n", err);
		goto free_exit;
        }
	KPU_INFO("kprobe registered  : %s\n", uitem->ui_kp.symbol_name);
	config_item_init_type_name(&uitem->ui_item, name, &kpu_item_type);
	return &uitem->ui_item;

free_exit:
	if (buf) {
		kfree(buf);
	}
	if (uitem) {
		kfree(uitem);
	}
	return ERR_PTR(err);
}

static struct configfs_attribute kpu_kprobe_attr_description = {
	.ca_owner = THIS_MODULE,
	.ca_name = "description",
	.ca_mode = S_IRUGO,
};

static struct configfs_attribute *kpu_kprobe_attrs[] = {
	&kpu_kprobe_attr_description,
	NULL,
};

static ssize_t
kpu_kprobe_attr_show(struct config_item *item, struct configfs_attribute *attr, char *page)
{
	return sprintf(page,
"[kprobes_util]\n"
"\n"
"This subsystem provides an userspace interface for kprobes.\n"
);
}

static void
kpu_kprobe_release(struct config_item *item)
{
	kfree(kpu_group_of(item));
}

static struct configfs_item_operations kpu_kprobe_item_ops = {
	.release	= kpu_kprobe_release,
	.show_attribute	= kpu_kprobe_attr_show,
};

static struct configfs_group_operations kpu_kprobe_group_ops = {
	.make_item	= kpu_kprobe_make_item,
};

static struct config_item_type kpu_kprobe_type = {
	.ct_item_ops	= &kpu_kprobe_item_ops,
	.ct_group_ops	= &kpu_kprobe_group_ops,
	.ct_attrs	= kpu_kprobe_attrs,
	.ct_owner	= THIS_MODULE,
};

static struct configfs_subsystem kpu_kprobe_subsys = {
	.su_group = {
		.cg_item = {
			.ci_namebuf = "kprobe",
			.ci_type = &kpu_kprobe_type,
		},
	},
};

static struct configfs_subsystem *kpu_subsys[] = {
	&kpu_kprobe_subsys,
	NULL,
};

static int __init
kprobes_util_init(void)
{
	int ret;
	int i;
	struct configfs_subsystem *subsys;

	for (i = 0; kpu_subsys[i]; i++) {
		subsys = kpu_subsys[i];

		config_group_init(&subsys->su_group);
		mutex_init(&subsys->su_mutex);
		ret = configfs_register_subsystem(subsys);
		if (ret) {
			printk(KERN_ERR "Error %d while registering subsystem %s\n",
			       ret,
			       subsys->su_group.cg_item.ci_namebuf);
			goto out_unregister;
		}
	}
	return 0;

out_unregister:
	for (; i >= 0; i--) {
		configfs_unregister_subsystem(kpu_subsys[i]);
	}
	return ret;
}

static void __exit
kprobes_util_exit(void)
{
	int i;

	for (i = 0; kpu_subsys[i]; i++) {
		configfs_unregister_subsystem(kpu_subsys[i]);
	}
}

module_init(kprobes_util_init);
module_exit(kprobes_util_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yoshifumi Shimono <yoshifumi.shimono@gmail.com>");
MODULE_DESCRIPTION("Kprobes utility module.");
